/*** Warning, some information in this document has changed ***/
/************ Contact SciComp for latest version *************/
Now that PopUpFuncs has the ability to actually change text in a file, many different text altering utilities can (and should) be written or integrated into the PUF environment. Examples are block commenting, comment wrapping, pretty-printing, C-style prototypes, list of routines. In order to allow developers to write their utilities (and to avoid having to write them ourselves), we developed the following specification, by which PopUpFuncs can access external code routines to perform operations on the selected text.
Since Apple has graciously sewed up the name "xcmd", PopUpFuncs external commands are henceforth called XPOPs, for "external piece of program".
The format of the xpop call is:
pascal void main( XPopRecPtr popRec );
where XPopRecPtr is a pointer to a record with the following fields:
typedef struct {
long private; /* preserved by PUF, for xpop use */
short message; /* selection message */
long result; /* place for xpop to put the return value (if any) */
long param; /* depends on message */
} XPopRec,*XPopRecPtr;
On entry, A0 will point to the beginning of the code resource.
The xpop is allowed to alter the normal registers (D0-D2,A0-A1), should save the rest, can count on A5 pointing to a valid Quickdraw environment, and can count on itself being locked down in the application heap.
XPOPs can be unloaded between messages. Global variables stuffed in the xpop resource could therefore be reinitialized between messages. Use these globals if you wish, but don't expect them to retain their values between calls. If you wish to keep globals around between messages, allocate a pointer or handle to a record structure, and return it in 'private'.
Message-by-message description
init
PUF will call each xpop once with an init message. Any once-only type activities should be done at this time. Private will be NIL on entry, and will be preserved for the rest of the calls.
On entry, param will point to a query record (only some of the fields will be appropriate).
To minimize the number of times we load the resources for query calls, the xpop returns a pointer (allocated with NewPtr) to a structure that will contain flags indicating its constant needs.
char version; { version of the record structure, this version = 1 }
long language; { bit flags, 32 different languages }
Boolean writes; { intends to write, don't bother if file locked }
This structure is used to decide if a query is needed at mouseDown time.
If the xpop fails to load, doesn't like the machine environment, or for any other reason decides that it just can't cope, it should return a -1.
dispose
PUF will call each xpop once to allow them to dispose of memory they may have allocated, flush buffers, and tidy up. For MPW, this call will come when MPW exits. For THINK C, this call will come when the project window closes. The xpop should return the appropriate OSErr code.
fopen
fclose
If an xpop wants to know about file openings and closings, as indicated in the 'getFileMsgs' field of the record returned by the init call, it will receive these two messages as a window opens and closes. On entry, param will be the WindowPeek for the window concerned. In the case of a rename, an fclose will be issued, followed by an fopen.
query
At invocation, PUF will scan for available xpops and query each with a query message as to whether it can deal with the current situation. With a query message, param will be a pointer to a record with the following fields.
char version; { version of the record structure, this version = 1 }
StringPtr fileName;
long dirID;
long selStart, selEnd;
Handle first512; { the first 512 characters }
char language;
char environment; { kMPW or kTHINK }
Boolean writeLocked;
In response to the query message, the xpop returns a 0 if it doesn't want to deal with the current situation, and a StringPtr to the name it wants to insert in the menu if it does. For example, "comment" could examine the text to determine whether to call itself "comment" or "uncomment".
The StringPtr is allowed to point to a constant compiled into the xpop code, PUF will copy the string before unloading or unlocking the xpop code. If an XPOP allocates memory for this string, unless it is in a purgeable resource, it should deallocate the memory on the next message, as PUF will not attempt to deallocate the memory pointed to by StringPtr, and multiple calls to query will eventually clog memory.
doit
With a doit message, param will be a pointer to a record with the following fields:
char version; { version of the record structure, this version = 1 }
StringPtr fileName;
long dirID;
long selStart, selEnd;
Handle text; /* handle containing the text */
char language;
char environment; /* MPW or THINK */
Boolean writeLocked;
The xpop should return a pointer (allocated with NewPtr and placed in ) to a record with the following fields.
char version; { version of the record structure, this version = 1 }
Boolean paste; { should PUF paste the contents of the handle? }
char where; { before, after, replace }
After handling the return, PUF will call DisposPtr on the pointer.
help
There will be a dialog off the about box that handles xpop help. XPOPs are listed by name, and can be chosen. On a single click, PUF sends a help message with the return set to 0. The xpop returns a pointer to a one-line description of what it does. If not handled, PUF displays "no description available". On a double click, PUF sends a help message with the return value set to 1. XPOP can do its own window or can return a ptr to a block of text that PUF will display in a scrolling text field. If return value is still 1 (meaning the xpop didn't handle the message), PUF displays "No Help available". Otherwise, if return value is non-nil, PUF displays it.
whole file commands ?
What about commands that deal with a whole file? It would be awkward (and dangerous) to try to use the clipboard-paste mechanism for large files. Could an xpop create a new file and have PUF substitute if for the old one? The worst abuse I forsee is pretty-printing a whole file.
selection region adjustment
Somehow, the xpop should also indicate whether it would prefer to have only whole lines selected. If so, PUF would adjust the current selection to start at the first character of the first line and extend up to and including the carriage return at the end of the last line.
XPOPs also need to indicate whether they want a paste following their mucking about. If there IS a paste, should PUF always reset the selection, or should the xpop get to choose? By resetting the selection, PUF can let users chose another xpop immediately.